﻿using ImCommon;
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ImServer {
    // 服务器
    class Server: Socket {
        private SQLiteConnection mDatabase;                         // 数据库
        private readonly Dictionary<string, Client> mOnlineUsers;   // 在线用户列表

        /**
         * 构造方法。
         */
        public Server() :
            base(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) {
            // 初始化成员
            mOnlineUsers = new Dictionary<string, Client>();

            // 创建数据库
            this.CreateDatabase();
        }

        /**
         * 析构方法。
         */
        ~Server() {
            // 关闭套接字
            base.Close();

            // 关闭数据库
            mDatabase.Close();

            // 清除在线用户列表
            mOnlineUsers.Clear();
        }

        /**
         * 创建数据库。
         */
        private void CreateDatabase() {
            // 打开数据库
            string databasePath = Path.GetFullPath(Environment.CurrentDirectory + Const.DB_PATH);   // 数据库文件路径
            string connectionString = "Data Source =" + databasePath;                               // 连接信息
            mDatabase = new SQLiteConnection(connectionString);
            mDatabase.Open();

            // 创建表
            /* 用户表 */
            string sql = string.Format("CREATE TABLE IF NOT EXISTS {0}({1} TEXT PRIMARY KEY,{2} TEXT,{3} INTEGER)",
                Const.TABLE_USERS, Const.FIELD_NAME, Const.FIELD_PASSWORD, Const.FIELD_ICON_INDEX);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            /* 好友表 */
            sql = string.Format("CREATE TABLE IF NOT EXISTS {0}({1} TEXT,{2} TEXT)",
                Const.TABLE_FRIENDS, Const.FIELD_OWNER, Const.FIELD_FRIEND_NAME);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            /* 离线聊天表 */
            sql = string.Format("CREATE TABLE IF NOT EXISTS {0}({1} TEXT,{2} TEXT,{3} TEXT,{4} TEXT)",
                Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, Const.FIELD_SRC_USER, Const.FIELD_CONTENT, Const.FIELD_TIME);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            /* 离线添加好友表 */
            sql = string.Format("CREATE TABLE IF NOT EXISTS {0}({1} TEXT,{2} TEXT,{3} INTEGER)",
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, Const.FIELD_DST_USER, Const.FIELD_RESULT);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
        }

        /**
         * 处理添加好友请求。
         * @param request   添加好友请求
         * @param client    客户端
         */
        private void HandleAddFriendRequest(AddFriendRequest request, Client client) {
            SQLiteDataReader query;
            string sql;

            // 检查目标用户是否存在
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}'", Const.TABLE_USERS, Const.FIELD_NAME, request.mDstUsername);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            if (!query.Read()) {
                // 查无此人，通知源用户
                AddFriendResult result = new AddFriendResult(0, request.mDstUsername, ResultCode.USER_NOT_FOUND, null);
                this.SendData(client, result);
                query.Close();
                return;
            }
            query.Close();

            // 检查是否已经是好友了
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}' AND {3}='{4}'", 
                Const.TABLE_FRIENDS, Const.FIELD_OWNER, request.mSrcUsername, Const.FIELD_FRIEND_NAME, request.mDstUsername);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            if (query.Read()) {
                // 已经是好友了，通知源用户
                AddFriendResult result = new AddFriendResult(0, request.mDstUsername, ResultCode.ALREADY_BE_FRIENDS, null);
                this.SendData(client, result);
                query.Close();
                return;
            }
            query.Close();

            // 如果目标用户不在线，则将请求信息插入到数据库中
            Client friendUser;
            bool isOnline = mOnlineUsers.TryGetValue(request.mDstUsername, out friendUser);
            if (!isOnline) {
                sql = string.Format("INSERT INTO {0}({1},{2}) VALUES('{3}','{4}')", 
                    Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, Const.FIELD_DST_USER, request.mSrcUsername, request.mDstUsername);
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
                return;
            }

            // 向目标用户发送请求
            this.SendData(friendUser, request);
        }

        /**
         * 处理添加好友结果。
         * @param result    添加好友结果
         */
        private void HandleAddFriendResult(AddFriendResult result) {
            string sql;

            // 如果结果是同意，则将相关信息写到好友表中
            if (result.mResultCode == ResultCode.ADD_FRIEND_ALLOW) {
                sql = string.Format("INSERT INTO {0} VALUES('{1}','{2}')", Const.TABLE_FRIENDS, result.mSrcUsername, result.mDstUsername);
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

                sql = string.Format("INSERT INTO {0} VALUES('{1}','{2}')", Const.TABLE_FRIENDS, result.mDstUsername, result.mSrcUsername);
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
            }

            // 如果目标用户不在线，则将结果信息插入到数据库中
            Client friendUser;
            bool isOnline = mOnlineUsers.TryGetValue(result.mSrcUsername, out friendUser);
            if (!isOnline) {
                sql = string.Format("INSERT INTO {0} VALUES('{1}','{2}','{3}')", 
                    Const.TABLE_OFFLINE_ADD_FRIEND, result.mSrcUsername, result.mDstUsername, (int)result.mResultCode);
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
                return;
            }

            // 向目标用户发送结果
            this.SendData(friendUser, result);
        }

        /**
         * 处理聊天。
         * @param chatInfo  聊天信息
         */
        private void HandleChat(ChatInfo chatInfo) {
            // 处理群发消息
            if (chatInfo.mDstUsername == ImProtocol.MULTI_CHAT) {
                foreach (string username in mOnlineUsers.Keys) {
                    // 跳过自己，向其它用户发送聊天信息
                    if (username != chatInfo.mSrcUsername) {
                        this.SendData(mOnlineUsers[username], chatInfo);
                    }
                }
                return;
            }

            // 如果目标用户不在线，则将聊天信息插入到数据库中
            Client friendUser;
            bool result = mOnlineUsers.TryGetValue(chatInfo.mDstUsername, out friendUser);
            if (!result) {
                string sql = string.Format("INSERT INTO {0} VALUES('{1}','{2}','{3}','{4}')",
                    Const.TABLE_OFFLINE_CHAT, chatInfo.mDstUsername, chatInfo.mSrcUsername, chatInfo.mContent, chatInfo.mTime);
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
                return;
            }

            // 发送给目标用户
            this.SendData(friendUser, chatInfo);
        }
        
        /**
         * 处理离线请求。
         * @param request   离线请求
         */
        private void HandleLogoff(LogoffRequest request) {
            // 将该用户从在线列表移除
            mOnlineUsers.Remove(request.mUsername);
            
            // 广播该用户的离线请求
            foreach (Client user in mOnlineUsers.Values) {
                this.SendData(user, request);
            }
        }

        /**
         * 处理登录请求。
         * @param request   登录请求
         * @param client    客户端
         */
        private void HandleLogon(LogonRequest request, Client client) {
            LogonResult logonResult = new LogonResult();
            SQLiteDataReader query;
            string sql;

            // 检查该用户是否已经登录
            Client onlineUser;
            bool result = mOnlineUsers.TryGetValue(request.mUserInfo.mUsername, out onlineUser);
            if (result) {
                // 已经登录了
                logonResult.mResultCode = ResultCode.USER_ALREADY_LOGON;
                this.SendData(client, logonResult);
                return;
            }

            // 从数据库中查找该用户
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}'", Const.TABLE_USERS, Const.FIELD_NAME, request.mUserInfo.mUsername);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            if (!query.Read()) {
                // 查无此人
                query.Close();
                logonResult.mResultCode = ResultCode.USER_NOT_FOUND;
                this.SendData(client, logonResult);
                return;
            }

            // 比对密码
            string password = query.GetString(query.GetOrdinal(Const.FIELD_PASSWORD));
            if (!password.Equals(request.mUserInfo.mPassword)) {
                // 密码不正确
                query.Close();
                logonResult.mResultCode = ResultCode.PASSWORD_WRONG;
                this.SendData(client, logonResult);
                return;
            }

            // 登录成功，获取该用户的相关信息
            logonResult.mResultCode = ResultCode.LOGON_SUCCEEDED;
            logonResult.mIconIndex = query.GetInt32(query.GetOrdinal(Const.FIELD_ICON_INDEX));
            query.Close();

            // 获取该用户的好友列表
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}'", Const.TABLE_FRIENDS, Const.FIELD_OWNER, request.mUserInfo.mUsername);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();

            int friendCount;
            List<UserInfo> friendList = new List<UserInfo>();
            string friendName;
            int iconIndex;
            SQLiteDataReader iconIndexQuery;
            bool isOnline;
            List<bool> onlineList = new List<bool>();

            for (friendCount = 0; query.Read(); friendCount++) {
                friendName = query.GetString(query.GetOrdinal(Const.FIELD_FRIEND_NAME));

                sql = string.Format("SELECT {0} FROM {1} WHERE {2}='{3}'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, friendName);
                iconIndexQuery = new SQLiteCommand(sql, mDatabase).ExecuteReader();
                iconIndexQuery.Read();
                iconIndex = iconIndexQuery.GetInt32(iconIndexQuery.GetOrdinal(Const.FIELD_ICON_INDEX));
                iconIndexQuery.Close();

                UserInfo userInfo = new UserInfo(iconIndex, null, friendName);
                friendList.Add(userInfo);

                Client friendUser;
                isOnline = mOnlineUsers.TryGetValue(friendName, out friendUser);
                onlineList.Add(isOnline);

                // 如果好友在线，则向好友发送通知
                if (isOnline) {
                    FriendLogon friendLogon = new FriendLogon(request.mUserInfo.mUsername);
                    this.SendData(friendUser, friendLogon);
                }
            }
            query.Close();
            logonResult.mFriendCount = friendCount;
            logonResult.mFriends = friendList;
            logonResult.mFriendsOnline = onlineList;

            // 发送登录结果
            this.SendData(client, logonResult);

            // 查询是否有和该用户相关的离线添加好友请求，有则发送
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}' AND {3} IS NULL", 
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            string srcUsername;
            while (query.Read()) {
                srcUsername = query.GetString(query.GetOrdinal(Const.FIELD_SRC_USER));
                sql = string.Format("SELECT {0} FROM {1} WHERE {2}='{3}'", Const.FIELD_ICON_INDEX, Const.TABLE_USERS, Const.FIELD_NAME, srcUsername);
                iconIndexQuery = new SQLiteCommand(sql, mDatabase).ExecuteReader();
                iconIndexQuery.Read();

                AddFriendRequest addFriendRequest = new AddFriendRequest(request.mUserInfo.mUsername, 
                    iconIndexQuery.GetInt32(iconIndexQuery.GetOrdinal(Const.FIELD_ICON_INDEX)), srcUsername);
                this.SendData(client, addFriendRequest);
                iconIndexQuery.Close();
            }
            query.Close();

            // 删除数据库中的离线添加好友请求
            sql = string.Format("DELETE FROM {0} WHERE {1}='{2}'", Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            // 查询是否有和该用户相关的离线添加好友结果，有则发送
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}' AND {3} IS NOT NULL", 
                Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername, Const.FIELD_RESULT);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            string dstUsername;
            while (query.Read()) {
                dstUsername = query.GetString(query.GetOrdinal(Const.FIELD_DST_USER));
                ResultCode resultCode = (ResultCode)query.GetInt32(query.GetOrdinal(Const.FIELD_RESULT));
                AddFriendResult addFriendResult = new AddFriendResult(-1, dstUsername, resultCode, request.mUserInfo.mUsername);
                this.SendData(client, addFriendResult);
            }
            query.Close();

            // 删除数据库中的离线添加好友结果
            sql = string.Format("DELETE FROM {0} WHERE {1}='{2}'", Const.TABLE_OFFLINE_ADD_FRIEND, Const.FIELD_SRC_USER, request.mUserInfo.mUsername);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            // 查询是否有和该用户相关的离线聊天消息，有则发送
            sql = string.Format("SELECT * FROM {0} WHERE {1}='{2}'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            query = new SQLiteCommand(sql, mDatabase).ExecuteReader();
            while (query.Read()) {
                srcUsername = query.GetString(query.GetOrdinal(Const.FIELD_SRC_USER));
                string content = query.GetString(query.GetOrdinal(Const.FIELD_CONTENT));
                string time = query.GetString(query.GetOrdinal(Const.FIELD_TIME));
                ChatInfo chatInfo = new ChatInfo(content, request.mUserInfo.mUsername, srcUsername, time);
                this.SendData(client, chatInfo);
            }
            query.Close();

            // 删除数据库中的离线聊天信息
            sql = string.Format("DELETE FROM {0} WHERE {1}='{2}'", Const.TABLE_OFFLINE_CHAT, Const.FIELD_DST_USER, request.mUserInfo.mUsername);
            new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();

            // 将该用户加入在线列表
            mOnlineUsers.Add(request.mUserInfo.mUsername, client);
        }

        /**
         * 处理注册请求。
         * @param request   注册请求
         * @param client    客户端
         */
        private void HandleReg(RegRequest request, Client client) {
            RegResult regResult = new RegResult();

            // 将该用户信息插入到数据库中
            string sql = string.Format("INSERT INTO {0} VALUES('{1}','{2}','{3}')", 
                Const.TABLE_USERS, request.mUserInfo.mUsername, request.mUserInfo.mPassword, request.mUserInfo.mIconIndex);
            try {
                new SQLiteCommand(sql, mDatabase).ExecuteNonQuery();
            } catch (Exception) {
                // 该用户已存在
                regResult.mResultCode = ResultCode.USER_EXISTS;
                this.SendData(client, regResult);
                return;
            }

            // 发送结果
            regResult.mResultCode = ResultCode.REG_SUCCEEDED;
            this.SendData(client, regResult);

            // 将该用户加入到在线列表中
            mOnlineUsers.Add(request.mUserInfo.mUsername, client);
        }

        /**
         * 客户端连接服务器事件的响应方法。
         * @param result    事件相关信息
         */
        private void OnAccept(IAsyncResult result) {
            try {
                Socket listener = (Socket)result.AsyncState;
                Socket clientSocket = listener.EndAccept(result);
                base.BeginAccept(new AsyncCallback(OnAccept), this);
                SocketInformation si = clientSocket.DuplicateAndClose(Process.GetCurrentProcess().Id);
                Client client = new Client(this, si);
            } catch (Exception) {
            }
        }
        
        /**
         * 处理客户端发送来的数据。
         * @param pro       协议
         * @param client    客户端
         */
        public void OnGetMessageFromClient(ImProtocol pro, Client client) {
            lock (this) {
                switch (pro.mType) {
                    case ImProtocol.Type.ADD_FRIEND_REQUEST:
                        this.HandleAddFriendRequest((AddFriendRequest)pro, client);
                        break;
                    case ImProtocol.Type.ADD_FRIEND_RESULT:
                        this.HandleAddFriendResult((AddFriendResult)pro);
                        break;
                    case ImProtocol.Type.CHAT:
                        this.HandleChat((ChatInfo)pro);
                        break;
                    case ImProtocol.Type.LOGOFF:
                        this.HandleLogoff((LogoffRequest)pro);
                        break;
                    case ImProtocol.Type.LOGON:
                        this.HandleLogon((LogonRequest)pro, client);
                        break;
                    case ImProtocol.Type.REG:
                        this.HandleReg((RegRequest)pro, client);
                        break;
                }
            }
        }
        
        /**
         * 发送数据。
         * @param client    客户端
         * @param pro       协议
         */
        private void SendData(Client client, ImProtocol pro) {
            // 将协议转换成数据
            byte[] data = ImProtocol.ToBytes(pro);

            // 发送数据长度
            client.Send(BitConverter.GetBytes(data.Length));

            // 发送数据
            client.Send(data);

            // 防止发送过快导致客户端无法响应
            Thread.Sleep(100);
        }
        
        /**
         * 开启服务。
         * @return 成功则返回 true，否则返回 false
         */
        public bool Start() {
            // 绑定端口
            try {
                IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ImProtocol.SERVER_IP), ImProtocol.SERVER_PORT);
                base.Bind(endPoint);
            } catch (Exception) {
                return false;
            }

            // 侦听
            base.Listen(int.MaxValue);

            // 开始接受连接
            base.BeginAccept(new AsyncCallback(OnAccept), this);

            return true;
        }
    }
}
